/*
 * Copyright (C) Francisco Morero Peyrona. All rights reserved.
 *
 * This software is published under the terms of Open Source
 * License version 1.1, a copy of which has been included with this
 * distribution in the License.txt file.
 */

package jdbcmanager.dbtools.dbconnect;

import java.io.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

import jdbcmanager.app.*;
import jdbcmanager.swing.*;
import jdbcmanager.swing.help.*;
import jdbcmanager.dbtools.agentwise.*;

/**
 * A dialog that ask for all needed information to stablish a connection with a
 * DBMS for the first time (driver, URL, ...).
 * <p>
 * Drivers are loaded at runtime from the available drivers is "sqlagents"
 * directory.<br>
 * For more information, consult Agents related classes.
 *
 * Note: when there is no .connect file or all existing are malformed this dialog breaks down.
 *
 * @author Francisco Morero Peyrona
 */

public class JConnectDlg extends JxDialog
{
   private JPanel         pnlDriver     = new JPanel();
   private JLabel         lblDriver     = new JLabel();
   private JLabel         lblURL        = new JLabel();
   private JLabel         lblPassword   = new JLabel();
   private JLabel         lblUser       = new JLabel();
   private MyComboBox     cmbDriver     = new MyComboBox();    // Inner class
   private JButton        btnNewDriver  = new JButton();
   private JButton        btnEditDriver = new JButton();
   private JTextField     txtURL        = new JTextField();
   private JTextField     txtUser       = new JTextField();
   private JPasswordField txtPassword   = new JPasswordField();

   // User Predefined Connections
   private JButton        btnUPC;    // "Favorites >>" (user predefined connections)
   private JPanel         pnlUPC;    // UPC -> User Predefined Connection
   private JList          lstUPC;
   private JCheckBox      chkSavePass;

   private AgentDBMS[] aAgent;

   //-------------------------------------------------------------------------//
   
   /**
    * Initialises the Dialog with default values.
    */
   public JConnectDlg()
   {
      super( "Data Base Server Connection" );
      
      init();
   }

   /**
    * Needed to be redefined
    */
   public void setVisible( boolean b )
   {
      if( b && this.cmbDriver.getItemCount() > 0 )  // CARE! Only when setVisible( true )
         this.cmbDriver.setSelectedIndex( 0 );

      super.setVisible( b );
   }

   /**
    * 
    * @return
    */
   public ConnectionDefinition getConnectionDefinition()
   {
      return new ConnectionDefinition( getAgent().getDriverId(),
                                       this.txtURL.getText().trim(),
                                       this.txtUser.getText().trim(),
                                       new String( this.txtPassword.getPassword() ),
                                       null );
   }

   /**
    * See <code>JxDialog.onAccept(...)</code>
    */
   protected void onAccept( ActionEvent evt )
   {
      if( this.txtURL.getText().trim().length() == 0 )
         App.showMessage( "Incomplete information", "URL can not be empty" );
      else
         super.onAccept( evt );
   }

   //-------------------------------------------------------------------------//
   // PRIVATES
   //-------------------------------------------------------------------------//

   private AgentDBMS getAgent()
   {
      int nSelected = this.cmbDriver.getSelectedIndex();
      
      if( nSelected > -1 )
         return this.aAgent[ nSelected ];
      
      return null;
   }

   // Called when "new driver" or "edit driver" buttons are pressed
   private void onDriverManager( boolean bNew )
   {
      JDriverMangerDlg dlg;
      String           sID;
         
      if( bNew )
      { 
         sID = String.valueOf( System.currentTimeMillis() );
         dlg = new JDriverMangerDlg();
      }
      else
      {
         AgentDBMS agent = getAgent();
         
         sID = agent.getDriverId();
         dlg = new JDriverMangerDlg( agent.getDriverFamiliarName(), agent.getConnectionSyntax(), 
                                     agent.getDriverClassName(), agent.getJARs(),
                                     agent.getAgentMetaDataClassName(), agent.getAgentSQLClassName(), 
                                     agent.getAgentsJAR() );
      }
      
      dlg.pack( 400, 0 );
      dlg.center();
      dlg.setVisible( true );
      
      if( dlg.getCloseOption() == JxDialog.ACCEPTED )
      {         
         AgentDBMS.createAgent( dlg.getProductName(), sID, dlg.getConnectionSyntax(), dlg.getDriverClassName(), 
                                dlg.getJARs(), dlg.getAgentMetaDataClassName(), dlg.getAgentSQLClassName(), dlg.getAgentsJAR() );
         reloadAgents();
      }
   }
   
   // Called when changes selected item in the driver combo box
   private void onDbDriverChanged()
   {
      AgentDBMS agent = getAgent();
      
      if( agent != null )
      {
         this.txtURL.setText( agent.getConnectionSyntax() );
         this.txtURL.setCaretPosition( 0 );
         this.txtUser.setText( "" );
         this.txtPassword.setText( "" );
      
         enableBasedOnAgentIsOperable( agent.couldOperate() );
      }
   }

   // Called when btn Save is clicked: saves current definition in a file
   private void onUPCsave( String sFileName )
   {
       String sName = null;
       
      if( sFileName.length() == 0 )
         sName = App.getStringUsingDialog( "Saving 'Connection Definition File'", 
                                           "Connection definition name", sFileName, false );
      else
         sName = sFileName;
      
      if( sName != null )
      {
         AgentDBMS agent     = (AgentDBMS) this.cmbDriver.getSelectedItem();
         boolean   bSavePass = this.chkSavePass.isSelected();
      
         ConnectionDefinition cd = new ConnectionDefinition();
                              cd.setDriverId( agent.getDriverId() );
                              cd.setURL( this.txtURL.getText() );
                              cd.setUser( this.txtUser.getText() );
                              cd.setPassword( bSavePass ? new String( this.txtPassword.getPassword() ) : "" );
                              cd.save( sName );
         reloadUPCList();
         this.lstUPC.setSelectedValue( sName, true );   // Makes the new file the selecetd one
      }
   }

   // Called when btn Delete is clicked: deletes current definition in a file
   private void onUPCdelete()
   {
      if( this.lstUPC.getSelectedIndex() > -1 ) 
      {
         if( App.confirmDialog( "Confirm to delete", "Delete '"+ this.lstUPC.getSelectedValue() +"'?" ) )
         {
            String sFileName = (String) this.lstUPC.getSelectedValue();
         
            if( ConnectionDefinition.delete( sFileName ) )
               reloadUPCList();
         }
      }
      else
      {
         App.showMessage( "Can't process", "No file is selected: please select one." );
      }
   }

   // Called when btn Predefined is clicked: shows or hide the UPC panel
   private void onUPCtoggle()
   {
      if( this.pnlUPC.isVisible() )
      {
         this.pnlUPC.setVisible( false );
         setSize( getWidth(), getHeight() - 180 );
         this.btnUPC.setText( this.btnUPC.getText().replace( '<', '>' ) );
      }
      else
      {
         setSize( getWidth(), getHeight() + 180 );
         this.pnlUPC.setVisible( true );
         this.btnUPC.setText( this.btnUPC.getText().replace( '>', '<' ) );
         reloadUPCList();
         
         // Hint for the user
         if( this.lstUPC.getModel().getSize() == 0 )
         {
            App.showMessage( "This list stores pre-defined connections.\n" +
                              "To add a new one, simply fill all the information needed\n" +
                              "to stablish a connection and click \"Save\" button." );
         }
         else
         {
            this.lstUPC.grabFocus();
         }
      }
   }

   // Called when UPC listbox changes its selected item
   private void onUPCchanged( ListSelectionEvent evt )
   {
      if( ! evt.getValueIsAdjusting() )   // There is one item selected
      {
         if( this.lstUPC.getSelectedIndex() >= 0 )
         {
            String sFileName = (String) this.lstUPC.getSelectedValue();
            
            updateControls( new ConnectionDefinition( sFileName ) );
         }
         else
         {
            this.cmbDriver.setSelectedIndex( 0 );   // This action initialises rest of the fields
         }
      }
   }
   
   // Update dialog controls based on information stored in passed ConnectionDefinition instance
   private void updateControls( ConnectionDefinition cd )
   {
      this.cmbDriver.setSelected( (cd.getDriverId() == null) ? "" : cd.getDriverId() );
      this.txtURL.setText(        (cd.getURL()      == null) ? "" : cd.getURL()      );
      this.txtUser.setText(       (cd.getUser()     == null) ? "" : cd.getUser()     );
      this.txtPassword.setText(   (cd.getPassword() == null) ? "" : cd.getPassword() );

      enableBasedOnAgentIsOperable( true );      // onDbDriverChanged(...) makes it disable sometimes
   }
   
   // Reloads combo box with available agents
   private void reloadAgents()
   {
      this.aAgent = AgentDBMS.getAgents();
      this.cmbDriver.removeAllItems();
   
      for( int n = 0; n < aAgent.length; n++ )
         this.cmbDriver.addItem( aAgent[n] );
   }
   
   // Reloads the user connections listbox (because user can add files on the fly or delete files manually)
   private void reloadUPCList()
   {
      int              nSelected = this.lstUPC.getSelectedIndex();
      File[]           aFile     = ConnectionDefinition.listFiles();
      DefaultListModel model     = (DefaultListModel) this.lstUPC.getModel();  
      
      model.removeAllElements();

      for( int n = 0; n < aFile.length; n++ )
      {
         String sFile = aFile[n].getName();
                sFile = sFile.substring( 0, sFile.lastIndexOf( '.' ) );   // Removes file extension

         model.addElement( sFile );
      }
      
      if( ! model.isEmpty() )
      {
          nSelected = Math.min( nSelected, model.getSize() - 1 );
          this.lstUPC.setSelectedIndex( (nSelected < 0) ? 0 : nSelected );
      }
   }
   
   protected void onHelp( ActionEvent evt )
   {
      new JHelpDialog( this, "ConnectDialog" );
   }
   
   // Enable or disable buttons
   private void checkButtonsStatus()
   {
      btnAccept.setEnabled(   this.aAgent.length > 0 );
      btnCancel.setEnabled(   this.aAgent.length > 0 );
      this.btnUPC.setEnabled( this.aAgent.length > 0 );
   }
   
   private void enableBasedOnAgentIsOperable( boolean bEnabled )
   {
      this.lblURL.setEnabled( bEnabled );
      this.txtURL.setEnabled( bEnabled );
      this.lblUser.setEnabled( bEnabled );
      this.txtUser.setEnabled( bEnabled );
      this.txtPassword.setEnabled( bEnabled );
      this.lblPassword.setEnabled( bEnabled );
   }
   
   // ------------------------- GUI initialisation -------------------------------
   private void init()
   {
      // Enables Help button
      btnHelp.setVisible( true );
      
      // Initialises Agents
      reloadAgents();
      
      // Create Toolbar for "Predefined" panel and add its buttons ------------
      JxToolBar toolbar = new JxToolBar();
                toolbar.setOrientation( SwingConstants.VERTICAL );
                toolbar.setFloatable( false );
                
      JButton btnNew = new JButton( new ImageIcon( getClass().getResource( "images/new.gif" ) ) );
              btnNew.setToolTipText( "Saves current connection definition into a new file" );
              btnNew.addActionListener( new ActionListener()
              {
                 public void actionPerformed( ActionEvent evt )
                 {
                    onUPCsave( "" );
                 }
              } );
      toolbar.add( btnNew );
            
      JButton btnSave = new JButton( new ImageIcon( getClass().getResource( "images/save.gif" ) ) );
              btnSave.setToolTipText( "Saves current connection definition into highlighted file" );
              btnSave.addActionListener( new ActionListener()
              {
                 public void actionPerformed( ActionEvent evt )
                 {
                    onUPCsave( (lstUPC.getSelectedIndex() != -1) ? (String) lstUPC.getSelectedValue() : "" );
                 }
              } );
      toolbar.add( btnSave );
      
      JButton btnSaveAs = new JButton( new ImageIcon( getClass().getResource( "images/save_as.gif" ) ) );
              btnSaveAs.setToolTipText( "Saves current connection definition in a new file" );
              btnSaveAs.addActionListener( new ActionListener()
              {
                 public void actionPerformed( ActionEvent evt )
                 {
                    onUPCsave( "" );
                 }
              } );
      toolbar.add( btnSaveAs );      

      JButton btnDelete = new JButton( new ImageIcon( getClass().getResource( "images/delete.gif" ) ) );
              btnDelete.setToolTipText( "Deletes highlighted connection definition file" );
              btnDelete.addActionListener( new ActionListener()
              {
                 public void actionPerformed( ActionEvent evt )
                 {
                    onUPCdelete();
                 }
              } );
      toolbar.add( btnDelete );

      JButton btnRefresh = new JButton( new ImageIcon( getClass().getResource( "images/refresh.gif" ) ) );
              btnRefresh.setToolTipText( "Reloads available connection definitions from disk" );
              btnRefresh.addActionListener( new ActionListener()
		      {
		         public void actionPerformed( ActionEvent evt )
		         {
		            reloadUPCList();
		         }
		      } );
      toolbar.add( btnRefresh );
      
      this.btnUPC = new JButton( "Favorites >>" );
      this.btnUPC.setMnemonic( 'F' );
      this.btnUPC.addActionListener( new ActionListener()
      {
         public void actionPerformed( ActionEvent evt )
         {
            onUPCtoggle();
         }
      } );

      pnlButtons.add( this.btnUPC );      
      //-------------------------------------------------------------------

      // Init panel for User Predefined Connections -----------------------
      this.chkSavePass = new JCheckBox( "Save also password to file (care: not secure)" );
      this.chkSavePass.setSelected( true );
      this.lstUPC      = new JList( new DefaultListModel() );
      this.lstUPC.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION );
      this.lstUPC.setBorder( BorderFactory.createEmptyBorder( 5,0,0,5 ) );
      this.lstUPC.addListSelectionListener( new ListSelectionListener()
      {
         public void valueChanged( ListSelectionEvent evt )
         {
            onUPCchanged( evt );
         }
      } );
      this.lstUPC.addMouseListener( new MouseAdapter()
      {
         public void mouseClicked( MouseEvent evt )
         {
            if( evt.getClickCount() == 2 )
               onAccept( null );
         }
      } );

      this.pnlUPC = new JPanel( new BorderLayout() );
      this.pnlUPC.setBorder( BorderFactory.createEmptyBorder( 9,3,0,0 ) ); 
      this.pnlUPC.add( new JLabel( "Favorite connections" ), BorderLayout.NORTH  );
      this.pnlUPC.add( new JScrollPane( this.lstUPC )      , BorderLayout.CENTER );
      this.pnlUPC.add( toolbar                             , BorderLayout.EAST   );
      this.pnlUPC.add( this.chkSavePass                    , BorderLayout.SOUTH  );

      this.pnlUPC.setVisible( false );
      //-------------------------------------------------------------------

      // Panel Driver controls --------------------------------------------
      Font font = new Font( "Courier", Font.PLAIN, 12 );
      
      this.pnlDriver.setLayout( null );
      this.lblDriver.setText( "DB Driver" );
      this.lblURL.setText( "URL" );
      this.lblPassword.setText( "Password" );
      this.lblUser.setText( "User" );
      
      this.btnNewDriver.setIcon( new ImageIcon( getClass().getResource( "images/new.gif" ) ) );
      this.btnNewDriver.setToolTipText( "Creates a new JDBC driver definition" );
      this.btnNewDriver.setMargin( new Insets( 0,0,0,0 ) );
      this.btnNewDriver.addActionListener( new ActionListener()
      {
         public void actionPerformed( ActionEvent evt )
         {
            onDriverManager( true );
         }
      } );

      this.btnEditDriver.setIcon( new ImageIcon( getClass().getResource( "images/edit.gif" ) ) );
      this.btnEditDriver.setMargin( new Insets( 0,0,0,0 ) );
      this.btnEditDriver.setToolTipText( "Edits highlighted JDBC driver definition" );
      this.btnEditDriver.addActionListener( new ActionListener()
      {
         public void actionPerformed( ActionEvent evt )
         {
            onDriverManager( false );
         }
      } );

      this.cmbDriver.addActionListener( new ActionListener()
      {
         public void actionPerformed( ActionEvent evt )
         {
            onDbDriverChanged();
         }
      });

      pnlDriver.add( this.lblDriver );
         this.lblDriver.setBounds( 0, 10, 50, 26 );
         this.lblDriver.setHorizontalAlignment( SwingConstants.RIGHT );
      pnlDriver.add( this.cmbDriver );
         this.cmbDriver.setBounds( 55, 10, 238, 26 );
      pnlDriver.add( this.btnNewDriver );
         this.btnNewDriver.setBounds( 299, 10, 26, 26 );
      pnlDriver.add( this.btnEditDriver );
         this.btnEditDriver.setBounds( 327, 10, 26, 26 );
      
      pnlDriver.add( this.lblURL );
         this.lblURL.setBounds( 0, 60, 50, 24 );
         this.lblURL.setHorizontalAlignment( SwingConstants.RIGHT );
      pnlDriver.add( this.txtURL );
         this.txtURL.setBounds( 55, 60, 299, 24 );
         this.txtURL.setFont( font );
         
      pnlDriver.add( this.lblUser );
         this.lblUser.setBounds( 0, 110, 50, 24 );
         this.lblUser.setHorizontalAlignment( SwingConstants.RIGHT );
      pnlDriver.add( this.txtUser );
         this.txtUser.setBounds( 55, 110, 140, 24 );
         this.txtUser.setFont( font );
         
      pnlDriver.add( this.lblPassword );
         this.lblPassword.setBounds( 210, 110, 55, 24 );
         this.lblPassword.setHorizontalAlignment( SwingConstants.RIGHT );
      pnlDriver.add( this.txtPassword );
         this.txtPassword.setBounds( 270, 110, 83, 24 );
         this.txtPassword.setFont( font );

      // Add elements
      JPanel pnl = new JPanel( new BorderLayout() );  // for all connection controls + all action buttons
             pnl.add( pnlDriver , BorderLayout.CENTER );
             pnl.add( pnlButtons, BorderLayout.SOUTH  );

      getContentPane().removeAll(); // removes buttons panel from parent
      getContentPane().add( pnl   , BorderLayout.CENTER );
      getContentPane().add( pnlUPC, BorderLayout.SOUTH  );
      
      checkButtonsStatus();
      
      setSize( 380, 230 );
      setResizable( false );
   }
   
   //-------------------------------------------------------------------------//
   // INNER CLASS: A JComboBox that 
   //                 * Uses a JLable as as renderer to show an icon
   //                 * Search for a string when JLabel is used as renderer
   //-------------------------------------------------------------------------//
   private final class MyComboBox extends JComboBox
   {
      MyComboBox()
      {
         setRenderer( new ComboBoxDriverRenderer() );
      }
    
      public void setSelected( String sDriverID )
      {
         for( int n = 0; n < getItemCount(); n++ )
         {
            AgentDBMS item = (AgentDBMS) getItemAt( n );
            
            if( item.getDriverId().equals( sDriverID ) )
            {
               setSelectedIndex( n );
               break;
            }
         }
      }
      
      public String getSelected()
      {
         AgentDBMS agent = (AgentDBMS) getSelectedItem();
         
         return agent.getDriverFamiliarName();
      }
   }  // End of inner classs: MyComboBox
   
   //-------------------------------------------------------------------------//
   // INNER CLASS: Drivers combox box renderer
   //-------------------------------------------------------------------------//
   private final class ComboBoxDriverRenderer extends JLabel implements ListCellRenderer
   {
      private ImageIcon iconOperable;
      private ImageIcon iconNonOperable;
      
      //---------------------------------------------//
      
      private ComboBoxDriverRenderer()
      {
         setOpaque( true );
         setHorizontalAlignment( LEFT );
         setVerticalAlignment( CENTER );
         
         try
         {
            this.iconOperable    = new ImageIcon( getClass().getResource( "images/driver_ok.gif"  ) );
            this.iconNonOperable = new ImageIcon( getClass().getResource( "images/driver_bad.gif" ) );
         }
         catch( Exception exc )
         {
            // Nothing to do: icon can't be found
         }
      }
      
      /**
       * This method finds the image and text corresponding to the selected 
       * value and returns the label, set up to display the text and image.
       */
      public Component getListCellRendererComponent( JList list, Object oValue, int nIndex,
                                                     boolean bSelected, boolean bCellHasFocus )
      {      
         if( bSelected )
         {
            setBackground( list.getSelectionBackground() );
            setForeground( list.getSelectionForeground() );
         }
         else
         {
            setBackground( list.getBackground() );
            setForeground( list.getForeground() );
         }
      
         //Set the icon and text
         ImageIcon icon  = ((AgentDBMS) oValue).couldOperate() ? this.iconOperable : this.iconNonOperable;
         String    sText = ((AgentDBMS) oValue).getDriverFamiliarName();
         
         setIcon( icon );
         setText( sText );
         setFont( list.getFont() );

         return this;
      }
   } // En of class: ComboBoxDriverRenderer
}